<template>
  <div style="height: calc(100vh - 272px);">
    <Row :gutter="16">
      <Col span="18">
        <div style="height:calc(100%);display:flex;">
          <div id="container" ref="container" class="container beautiful-bar grid" v-drag @contextmenu="showAreaMenu"
               @click="clickArea">
            <div class="fixed-tool">
              <node-box @addNode="addNode" ref="nodeBox" class="node-box"></node-box>
            </div>
            <div class="fixed-legend">
              <div class="legend-wrapper">
                <div class="legend-item">
                  <div class="legend item1"></div>
                  <br>
                  当前节点
                </div>
                <div class="legend-item">
                  <div class="legend item2"></div>
                  <br>
                  普通节点
                </div>
              </div>
            </div>
            <template v-for="node in nodeList">
              <flow-node :id="node.id" :key="node.id" :node="node" :current-element="currentElement"
                         @changeNodePosition="changeNodePosition" @clickNode="clickNode"
                         @contextmenu="showNodeMenu"></flow-node>
            </template>
            <div style="position:absolute;top: 2000px;">&nbsp;</div>
          </div>
        </div>
      </Col>
      <Col span="6" class="attribute-panel">

        <flow-attr ref="flowAttr" :current-element="currentElement" @change="changeLabel"
                   :fields="fields"></flow-attr>

      </Col>
    </Row>
    <vue-context-menu :context-menu-data="lineContextMenu" @delete="deleteElement"></vue-context-menu>
    <vue-context-menu :context-menu-data="nodeContextMenu" @delete="deleteElement"></vue-context-menu>
    <vue-context-menu :context-menu-data="areaContextMenu" @zoomIn="zoomIn" @zoomOut="zoomOut" @reDraw="reDraw"
                      v-if="!overLine" @showData="showData"></vue-context-menu>
    <flow-data-modal v-if="flowDataModalMount" v-model="flowDataModalShow"
                     :modal-data="flowDataModalData"></flow-data-modal>
  </div>

</template>

<script lang="ts">
import {Component, Watch, Prop} from 'vue-property-decorator';
import AbpBase from '@/lib/abpbase';
import {jsPlumb} from 'jsplumb';
import _ from 'lodash';
import FlowNode from './flow-node.vue';
import Node from '@/store/entities/flow/flow-data/node';
import Line from '@/store/entities/flow/flow-data/line';
import Info from '@/store/entities/flow/flow-data/info';
import NodeBox from './node-box.vue';
import Util from '@/lib/util';
import FlowAttr from './flow-attr.vue';
import FlowDataModal from './flow-data-modal.vue';
import Condition from '@/store/entities/flow/flow-data/condition';

@Component({
  components: {FlowAttr, NodeBox, FlowNode, FlowDataModal},
  directives: {
    drag: {
      bind(element, binding, vNode, oldNode) {
        if (!binding) {
          return;
        }
        element.onmousedown = (e) => {
          if (e.button === 2) {
            return;
          }
          let disX = e.clientX;
          let disY = e.clientY;
          element.style.cursor = 'move';
          document.onmousemove = (event) => {
            event.preventDefault();
            const left = event.clientX - disX;
            disX = event.clientX;
            element.scrollLeft += -left;
            const top = event.clientY - disY;
            disY = event.clientY;
            element.scrollTop += -top;
          };
          document.onmouseup = (event) => {
            element.style.cursor = 'auto';
            document.onmousemove = null;
            document.onmouseup = null;
          };
        };
      },
    },
  },
})
export default class FlowArea extends AbpBase {
  name: string = 'FlowArea';
  @Prop({type: Array, default: () => []}) fields: any[];
  currentStep: number = 1;

  flowDataModalMount: boolean = false;
  flowDataModalShow: boolean = false;
  flowDataModalData: any = {};
  jsPlumb: any = null;
  loadFinish: boolean = false;
  currentElement: { name: string, type: string, node: Node, line: Line } = {
    name: '',
    type: 'area',
    node: null,
    line: null,
  };
  zoom: number = 1;
  overLine: boolean = false;
  lineContextMenu = {
    menuName: 'line-menu',
    axis: {
      x: null,
      y: null,
    },
    menulists: [
      {
        fnHandler: 'delete',
        btnName: '删除连线',
      },
    ],
  };
  nodeContextMenu = {
    menuName: 'node-menu',
    axis: {
      x: null,
      y: null,
    },
    menulists: [
      {
        fnHandler: 'delete',
        btnName: '删除结点',
      },
    ],
  };
  areaContextMenu = {
    menuName: 'area-menu',
    axis: {
      x: null,
      y: null,
    },
    menulists: [
      {
        fnHandler: 'zoomIn',
        btnName: '放大画布',
      }, {
        fnHandler: 'zoomOut',
        btnName: '缩小画布',
      }, {
        fnHandler: 'reDraw',
        btnName: '重绘流程',
      }, {
        fnHandler: 'export',
        btnName: '导出图片',
      }, {
        fnHandler: 'print',
        btnName: '打印流程',
      }, {
        fnHandler: 'showData',
        btnName: '流程信息',
      },
    ],
  };

  @Watch('currentElement')
  currentElementChange(newVal, oldVal) {
    switch (newVal.type) {
      case 'node':
      case 'area':
        this.jsPlumb.select().setPaintStyle(this.$config.jsPlumb.jsPlumbSetting.PaintStyle);
        break;
      case 'line':
        if (oldVal.type === 'node') {
          document.getElementById(oldVal.node.id).classList.remove('node-active');
        }
        break;
    }
  }


  get flowName() {
    return this.$store.state.flowData.name;
  }

  get nodeList() {
    return this.$store.state.flowData.nodeList;
  }

  get lineList() {
    return this.$store.state.flowData.lineList;
  }

  back() {
    this.currentStep--;
  }

  forward() {
    this.currentStep++;
  }


  jsPlumbInit() {
    this.jsPlumb.ready(() => {
      this.jsPlumb.importDefaults(this.$config.jsPlumb.jsPlumbSetting);
      this.jsPlumb.setSuspendDrawing(false, true);

      this.load();

      this.currentElement = {
        name: this.flowName,
        type: 'area',
        node: null,
        line: null,
      };
      this.jsPlumb.bind('click', (connection, originalEvent) => {
        this.setCurrentLine(connection);
      });

      this.jsPlumb.bind('connection', (info, originalEvent) => {
        const from = info.source.id;
        const to = info.target.id;
        if (this.loadFinish) {
          this.$store.commit('flowData/addLine', {
            line: {
              from: from,
              to: to,
              label: '',
              conditions: [new Condition()],
            },
          });
        }
      });

      this.jsPlumb.bind('connectionDetached', (info, originalEvent) => {
        this.deleteLine(info.sourceId, info.targetId);
      });

      this.jsPlumb.bind('connectionMoved', (info, originalEvent) => {
        this.changeLine(info.originalSourceId, info.originalTargetId);
      });

      this.jsPlumb.bind('contextmenu', (connection, originalEvent) => {
        this.hideMenu('vue-contextmenu-menuName-line-menu');
        this.setCurrentLine(connection);
        originalEvent.preventDefault();
        this.overLine = true;
        this.lineContextMenu.axis = {x: originalEvent.clientX, y: originalEvent.clientY};
      });

      this.jsPlumb.bind('beforeDrop', (info) => {
        const from = info.sourceId;
        const to = info.targetId;
        if (from === to) {
          this.$Message.error({
            background: true,
            content: '当前节点不支持连接自身',
            duration: this.$config.duration,
          });
          return false;
        }
        if (this.hasLine(from, to)) {
          this.$Message.error({
            background: true,
            content: '连线已存在，请勿重复创建',
            duration: this.$config.duration,
          });
          return false;
        }
        if (this.hasOppositeLine(from, to)) {
          this.$Message.error({
            background: true,
            content: '连线不支持回环，请检查',
            duration: this.$config.duration,
          });
          return false;
        }
        return true;
      });

      this.jsPlumb.bind('beforeDetach', (connection) => {
      });

      this.jsPlumb.setContainer(this.$refs.container);
    });
  }

  setCurrentLine(connection) {
    const line = this.lineList.find((item) => {
      return item.from === connection.sourceId && item.to === connection.targetId;
    });
    this.currentElement = {
      name: this.flowName,
      type: 'line',
      node: null,
      line: line,
    };

    this.jsPlumb.select().setPaintStyle(this.$config.jsPlumb.jsPlumbSetting.PaintStyle);
    connection.setPaintStyle({
      stroke: '#19be6b',
      strokeWidth: 2,
      outlineStroke: 'transparent',
      outlineWidth: 10,
    });
  }

  setCurrentNode(node) {
    this.currentElement = {name: this.flowName, type: 'node', node: node, line: null};
  }

  setArea() {
    this.currentElement = {name: this.flowName, type: 'area', node: null, line: null};
  }

  load() {
    for (const node of this.nodeList) {
      this.jsPlumb.makeSource(node.id, _.merge(this.$config.jsPlumb.jsPlumbSourceOptions, {}));
      this.jsPlumb.makeTarget(node.id, this.$config.jsPlumb.jsPlumbTargetOptions);
      if (!node.viewOnly) {
        this.jsPlumb.draggable(node.id, {
          containment: 'parent',
          stop: (element) => {
          },
        });
      }
    }
    for (const line of this.lineList) {
      const connectionParams = {
        source: line.from,
        target: line.to,
        label: line.label ? line.label : '',
      };
      this.jsPlumb.connect(connectionParams, this.$config.jsPlumb.jsPlumbConnectOptions);
    }
    this.$nextTick(() => {
      this.loadFinish = true;
    });
  }

  deleteLine(from, to) {
    this.$store.commit('flowData/deleteLine', {
      from: from,
      to: to,
    });
  }

  deleteNode(nodeId) {
    this.$M.confirm({
      title: '系统提示',
      content: '确定要删除选定的节点吗？',
      onOk: () => {
        this.$store.commit('flowData/deleteNode', {
          nodeId: nodeId,
        });
        this.$nextTick(() => {
          this.jsPlumb.removeAllEndpoints(nodeId);
        });
        this.setArea();
      },
      onCancel: () => {
      },
    });
    return true;
  }

  changeLine(oldFrom, oldTo) {
    this.deleteLine(oldFrom, oldTo);
  }

  hasLine(from, to) {
    for (const line of this.lineList) {
      if (line.from === from && line.to === to) {
        return true;
      }
    }
    return false;
  }

  hasOppositeLine(from, to) {
    return this.hasLine(to, from);
  }

  changeLabel(event, line) {
    const connection = this.jsPlumb.getConnections({source: line.from, target: line.to})[0];
    if (!line.label || line.label === '') {
      const labelOverlay = connection.getLabelOverlay();
      if (labelOverlay) {
        connection.removeOverlay(labelOverlay.id);
      }
    } else {
      connection.setLabel({
        label: line.label,
        cssClass: 'flow-label',
      });
    }
    this.lineList.forEach((item) => {
      if (item.from === line.from && item.to === line.to) {
        item.label = line.label;
      }
    });
  }

  deleteElement() {
    if (this.currentElement.type === 'node') {
      this.deleteNode(this.currentElement.node.id);

    } else if (this.currentElement.type === 'line') {
      this.$M.confirm({
        title: '系统提示',
        content: '确定要删除选定的连线吗？',
        onOk: () => {
          const connection = this.jsPlumb.getConnections({
            source: this.currentElement.line.from,
            target: this.currentElement.line.to,
          })[0];
          this.jsPlumb.deleteConnection(connection);
          this.setArea();
        },
        onCancel: () => {
        },
      });
    }
  }

  changeNodePosition(info) {
    for (const node of this.nodeList) {
      if (node.id === info.nodeId) {
        node.left = info.left;
        node.top = info.top;
      }
    }
  }

  clickNode(event, node) {
    event.stopPropagation();
    this.setCurrentNode(node);
  }

  addNode(event, boxNode, mousePosition) {
    const screenX = event.originalEvent.clientX;
    const screenY = event.originalEvent.clientY;
    const container = this.$refs.container as any;
    const containerRect = container.getBoundingClientRect();
    let left = screenX, top = screenY;
    if (left < containerRect.x || left > containerRect.width + containerRect.x || top < containerRect.y || top > containerRect.height + containerRect.y) {
      this.$Message.error({background: true, content: '请把选中的节点拖入画布中', duration: this.$config.duration,});
      return;
    }
    left = left - containerRect.x + container.scrollLeft;
    top = top - containerRect.y + container.scrollTop;
    left -= 85;
    top -= 16;
    const nodeId = this.getMaxId();
    const originalName = boxNode.name;
    let nodeName = originalName;
    let index = 1;
    while (index < 9999) {
      let repeat = false;
      for (const node of this.nodeList) {
        if (node.name === nodeName) {
          nodeName = originalName + index;
          repeat = true;
        }
      }
      if (repeat) {
        index++;
        continue;
      }
      break;
    }
    const newNode = {
      id: nodeId,
      name: nodeName,
      type: boxNode.type,
      typeName: boxNode.typeName,
      left: left + 'px',
      top: top + 'px',
      icon: boxNode.icon,
      viewOnly: false,
      info: new Info(),
    };
    const startNodes = this.nodeList.filter((item) => {
      return item.type === 'start';
    });
    const endNodes = this.nodeList.filter((item) => {
      return item.type === 'end';
    });
    if (startNodes.length > 0 && newNode.type === 'start') {
      this.$Message.error({background: true, content: '流程仅支持一个开始结点，目前已经存在开始结点', duration: this.$config.duration,});
      return false;
    }
    if (endNodes.length > 0 && newNode.type === 'end') {
      this.$Message.error({background: true, content: '流程仅支持一个结束结点，目前已经存在结束结点', duration: this.$config.duration,});
      return false;
    }
    this.$store.commit('flowData/addNode', {
      node: newNode,
    });
    this.$nextTick(() => {
      this.jsPlumb.makeSource(newNode.id, this.$config.jsPlumb.jsPlumbSourceOptions);
      this.jsPlumb.makeTarget(newNode.id, this.$config.jsPlumb.jsPlumbTargetOptions);
      this.jsPlumb.draggable(newNode.id, {
        containment: 'parent',
        stop: (element) => {
        },
      });
    });
  }

  hideMenu(className: string) {
    for (const element of document.getElementsByClassName('vue-contextmenu-listWrapper')) {
      if (!element.classList.contains(className)) {
        (element as any).style.display = 'none';
      }
    }
  }

  showNodeMenu(event, node) {
    this.hideMenu('vue-contextmenu-menuName-node-menu');
    this.setCurrentNode(node);
    event.preventDefault();
    event.stopPropagation();
    this.nodeContextMenu.axis = {x: event.clientX, y: event.clientY};
  }

  showAreaMenu(event) {
    this.hideMenu('vue-contextmenu-menuName-area-menu');
    this.overLine = false;
    event.preventDefault();
    this.$nextTick(() => {
      this.areaContextMenu.axis = {x: event.clientX, y: event.clientY};
    });

  }

  zoomIn() {
    return;
    if (this.zoom >= 1) {
      return;
    }
    this.zoom = this.zoom + 0.05;
    (this.$refs.container as any).style.transform = `scale(${this.zoom})`;
    this.jsPlumb.setZoom(this.zoom);
  }

  zoomOut() {
    return;
    if (this.zoom <= 0) {
      return;
    }
    this.zoom = this.zoom - 0.05;
    (this.$refs.container as any).style.transform = `scale(${this.zoom})`;
    this.jsPlumb.setZoom(this.zoom);
  }


  reDraw() {
    this.nodeList.forEach((item) => {
      this.jsPlumb.remove(item.id);
    });
    this.$store.commit('flowData/reset');
    const name = Util.guid();
    this.$store.commit('flowData/changeName', {
      name: name,
    });
    this.currentElement = {name: name, type: 'area', node: null, line: null};
  }

  x(items: any[]) {
    let result: boolean = true;
    for (let i = 0; i < items.length; i++) {
      if (typeof (items[i]) !== 'number') {
        result = false;
      }
    }
    return result;
  }

  getData() {
    const data = _.cloneDeep({name: this.flowName, nodeList: this.nodeList, lineList: this.lineList});
    data.nodeList = data.nodeList.map((item) => {
      if(!this.x(item.info.users)){
        item.info.users = item.info.users.map(i => i.id);
      }
      if(!this.x(item.info.roles)){
        item.info.roles = item.info.roles.map(i => i.id);
      }
      if(!this.x(item.info.copyUsers)){
        item.info.copyUsers = item.info.copyUsers.map(i => i.id);
      }
      if(!this.x(item.info.restrictRoles)){
        item.info.restrictRoles=item.info.restrictRoles.map(i=>i.id);
      }
      return item;
    });
    return data;
  }

  showData() {
    const data = _.cloneDeep({name: this.flowName, nodeList: this.nodeList, lineList: this.lineList});
    data.nodeList = data.nodeList.map((item) => {
      if(!this.x(item.info.users)){
        item.info.users = item.info.users.map(i => i.id);
      }
      if(!this.x(item.info.roles)){
        item.info.roles = item.info.roles.map(i => i.id);
      }
      if(!this.x(item.info.copyUsers)){
        item.info.copyUsers = item.info.copyUsers.map(i => i.id);
      }
      if(!this.x(item.info.restrictRoles)){
        item.info.restrictRoles=item.info.restrictRoles.map(i=>i.id);
      }
      return item;
    });
    this.flowDataModalData = data;
    this.flowDataModalMount = true;
    this.$nextTick(() => {
      this.flowDataModalShow = true;
    });
  }

  clickArea() {
    this.setArea();
  }

  getMaxId() {
    const Ids = this.nodeList.map((item) => {
      return item.id;
    });
    if (Ids.length === 0) {
      return '0001';
    } else {
      Ids.sort();
      return Util.prefixInteger((parseInt(Ids.pop(), 10) + 1), 4);
    }
  }

  validateFlow() {
    if (this.nodeList.length === 0) {
      this.$Message.error({background: true, content: '流程中至少要包含一个节点，请检查', duration: this.$config.duration,});
      return false;
    }
    for (const node of this.nodeList) {
      if (!node.name) {
        this.$Message.error({
          background: true,
          content: '编号为 ' + node.id + ' 的节点缺少节点名称，请检查',
          duration: this.$config.duration,
        });
        return false;
      }
      if (!node.info.allot) {
        this.$Message.error({
          background: true,
          content: '编号为 ' + node.id + ' 的节点缺少执行权限，请检查',
          duration: this.$config.duration,
        });
        return false;
      }
      if (node.info.copy && node.info.copyUsers.length === 0) {
        this.$Message.error({
          background: true,
          content: '编号为 ' + node.id + ' 的节点需要抄送而未选择抄送用户，请检查',
          duration: this.$config.duration,
        });
      }
      if (node.info.allot === 'DESIGNUSER') {
        if (!node.info.userText) {
          this.$Message.error({
            background: true,
            content: '编号为 ' + node.id + ' 的节点须指定用户而未选择用户，请检查',
            duration: this.$config.duration,
          });
          return false;
        }
        if (!node.info.joinType && node.info.users.length > 1) {
          this.$Message.error({
            background: true,
            content: '编号为 ' + node.id + ' 的节点涉及多人审批而未指定审批方式，请检查',
            duration: this.$config.duration,
          });
          return false;
        }
      }
      if (node.info.allot === 'DESIGNROLE') {
        if (!node.info.roleText) {
          this.$Message.error({
            background: true,
            content: '编号为 ' + node.id + ' 的节点须指定角色而未选择角色，请检查',
            duration: this.$config.duration,
          });
          return false;
        }
        if (!node.info.joinType) {
          this.$Message.error({
            background: true,
            content: '编号为 ' + node.id + ' 的节点涉及多人审批而未指定审批方式，请检查',
            duration: this.$config.duration,
          });
          return false;
        }
      }
      const startNodes = this.nodeList.filter((item) => {
        return item.type === 'start';
      });
      if (startNodes === 0) {
        this.$Message.error({background: true, content: '流程必须有一个开始节点，请检查', duration: this.$config.duration,});
        return false;
      }
      const endNodes = this.nodeList.filter((item) => {
        return item.type === 'end';
      });
      if (endNodes.length === 0) {
        this.$Message.error({background: true, content: '流程必须有一个结束节点，请检查', duration: this.$config.duration,});
        return false;
      }
    }
    const nodes = [];
    for (const line of this.lineList) {
      nodes.push(line.from);
      nodes.push(line.to);
    }
    for (const node of this.nodeList) {
      if (!nodes.find((item) => {
        return item === node.id;
      })) {
        this.$Message.error({
          background: true,
          content: '编号为 ' + node.id + ' 的节点没有连线，请检查',
          duration: this.$config.duration,
        });
        return false;
      }
    }
    return true;
  }

  mounted() {
    this.jsPlumb = jsPlumb.getInstance();
    this.$nextTick(() => {
      this.jsPlumb.reset();
      this.jsPlumbInit();
    });
  }


  created() {
    if (!this.flowName) {
      this.$store.commit('flowData/changeName', {
        name: Util.guid(),
      });
    }
  }
}
</script>


<style lang="less" scoped>
@import './styles/flow.less';

.container::v-deep .jtk-hover {
  cursor: pointer;
}

.container::v-deep .flow-label {
  border-radius: 4px;
  background-color: #ffffff;
  padding: 4px 8px;
  font: 10px sans-serif;
  color: #515a6e;
  box-sizing: border-box;
  box-shadow: 0 0 0 2px #dcdee2, 0 0 5px 4px rgba(0, 0, 0, 0.2);
}

.container::v-deep .flow-label-active {
  border-radius: 4px;
  background-color: #ffffff;
  padding: 4px 8px;
  font: 10px sans-serif;
  color: #515a6e;
  box-sizing: border-box;
  box-shadow: 0 0 0 2px #19be6b, 0 0 5px 4px rgba(0, 0, 0, 0.2);
}

.attribute-panel::v-deep .ivu-card-body{
  height:calc(100vh - 275px);
}
</style>
